#include "FKRoom.h"

#include <QThread>
//#include <QStandardItemModel>

#include "FKEvent.h"
#include "FKUser.h"
#include "FKActor.h"
#include "FKActorsModel.h"
#include "FKDataRecord.h"

#include "FKLogger.h"
#include "FKEventNames.h"
#include "FKModelRole.h"
#include "FKRoomStates.h"

#include <QStringList>
#include <QEventLoop>
#include <QTimer>
#include <QFile>

FK_EVENTS(FKRoom){
    events.insert(FKEventName::e_publicMessage,&FKRoom::E_publicMessage);
    events.insert(FKEventName::e_beginLoading,&FKRoom::E_beginLoading);
    events.insert(FKEventName::e_loadingProgress, &FKRoom::E_loadingProgress);
    events.insert(FKEventName::e_loadScene,&FKRoom::E_loadScene);
    events.insert(FKEventName::e_loadingComplete, &FKRoom::E_loadingComplete);
    events.insert(FKEventName::e_endLoading,&FKRoom::E_endLoading);
    events.insert(FKEventName::e_updateActorsModel,&FKRoom::E_updateActorsModel);
    events.insert(FKEventName::e_reinstallActorsModel,&FKRoom::E_reinstallActorsModel);

    events.insert(FKEventName::e_lose,&FKRoom::E_lose);
    events.insert(FKEventName::e_win,&FKRoom::E_win);

    actions.insert(FKEventName::a_beginLoading,&FKRoom::A_beginLoading);
    actions.insert(FKEventName::a_clientLoadingComplete, &FKRoom::A_clientLoadingComplete);

    props<<FKEventName::c_name;
    props<<FKEventName::c_messageHistory;
}

QLinkedList<FKRoom*> FKRoom::_roomsExist=QLinkedList<FKRoom*>();
QMap<QString,FKRoomModule*> FKRoom::_avaliableModules=QMap<QString,FKRoomModule*>();
const qint32 FKRoom::deleteTimeOut=5000;
const int FKRoom::_messageHistoryCapacity=20;

/*FKRoom::FKRoom(qint32 objectID):FKObject(0,objectID),_state(FKRoomStates::nonLoaded),_sceneLoaded(false){
     FK_CBEGIN
//     _thread=new QThread;
//     _thread->start();
//     _scene=0;
//     _worldReferent=0;
     servant=new Servant;
     connect(this,SIGNAL(recievedEvent(FKEvent*)),SLOT(manageEvent(FKEvent*)),Qt::QueuedConnection);
     connect(this,SIGNAL(recievedAction(FKEvent*)),SLOT(manageAction(FKEvent*)),Qt::QueuedConnection);
     addObject(this);
     _roomsExist.append(this);
     _deleteTimer=new QTimer(this);
     connect(_deleteTimer,SIGNAL(timeout()),SLOT(deleteFKObject()));
     FK_CEND
}*/

FKRoom::FKRoom(FKRoom* room, qint32 objectID):FKObject(0,objectID),_state(FKRoomStates::nonLoaded),_sceneLoaded(false){
     Q_UNUSED(room)
     FK_CBEGIN
//     _thread=new QThread;
//     _thread->start();
//     _scene=0;
//     _worldReferent=0;
     servant=0;
     connect(this,SIGNAL(recievedEvent(FKEvent*)),SLOT(manageEvent(FKEvent*)),Qt::QueuedConnection);
     connect(this,SIGNAL(recievedAction(FKEvent*)),SLOT(manageAction(FKEvent*)),Qt::QueuedConnection);
     addObject(this);
     _roomsExist.append(this);
     _deleteTimer=new QTimer(this);
     connect(_deleteTimer,SIGNAL(timeout()),SLOT(deleteFKObject()));
     FK_CEND
}

FKRoom::~FKRoom(){
    FK_DBEGIN
//    _f..remove(getID());
//    foreach(FKObject* obj,_objects)removeObject(obj);
//    _thread->quit();
//    _thread->deleteLater();
    _roomsExist.removeOne(this);
    if(servant)delete servant;
    FK_DEND
}

void FKRoom::setModel(FKActorsModel *model){
    _actorsModel=model;
    _actorsModel->initialize();
}

QStringList FKRoom::messageHistory() const{
    return _messageHistory;
}

void FKRoom::enqueEvent(FKEvent* event){
    emit recievedEvent(event);
}

void FKRoom::enqueAction(FKEvent* action){
    emit recievedAction(action);
}

void FKRoom::manageAction(FKEvent* action){
    FKObject* obj=_objects.value(action->object());
    if(obj){
        obj->processFKAction(action);
    }else{
        emit newEvent(FKEvent::errorEvent(action));
        action->deleteLater();
    }
}

void FKRoom::manageEvent(FKEvent* event){
    FKObject* obj=_objects.value(event->object());
    if(obj){
        obj->processFKEvent(event);
    }else{
        emit newEvent(FKEvent::errorEvent(event));
        event->deleteLater();
    }
}

//void FKRoom::internalSound(qint32 sample, qint32 volume){
//    Q_UNUSED(sample)
//    Q_UNUSED(volume)
//}

void FKRoom::addObject(FKObject* obj){
    _objects.insert(obj->getID(),obj);
//    if(_sceneLoaded && _state==FKRoomStates::loaded)obj->initObject();
    if(isActive())obj->initObject();
}

void FKRoom::addUser(FKUser* user){
    if(!servant){
        FK_MLOG("unable add user to room: no servant")
    }
//    if(_users.contains(user))return;
    _deleteTimer->stop();
    QModelIndex userRecordIndex=_actorsModel->recordIndexForID(-1);
    _actorsModel->setData(userRecordIndex,user->getID(),FKModelRole::objectID);
    _actorsModel->setData(userRecordIndex,user->userName(),FKModelRole::nameRole);
    refreshActorsModel(user);
    _users.append(user);
}

void FKRoom::removeObject(FKObject *obj){
    _objects.remove(obj->getID());
    obj->setRoom(0);
}

bool FKRoom::hasUser(FKUser *user){
    return _users.contains(user);
}

void FKRoom::removeUser(FKUser* user){
//    userGone(user);
    QString txt(_state==FKRoomStates::nonLoaded ? tr("Quit") : tr("Open"));
    QModelIndex userRecordIndex=_actorsModel->recordIndexForID(user->getID());
    _actorsModel->setData(userRecordIndex,-1,FKModelRole::objectID);
    _actorsModel->setData(userRecordIndex,txt,FKModelRole::nameRole);
    _users.removeOne(user);
    if(_users.isEmpty())_deleteTimer->start(deleteTimeOut);
}

void FKRoom::userConnected(FKUser *user){
    QModelIndex userRecordIndex=_actorsModel->recordIndexForID(user->getID());
    if(userRecordIndex.isValid())_actorsModel->setData(userRecordIndex,user->userName(),FKModelRole::nameRole);
}

void FKRoom::userDisconnected(FKUser *user){
    QModelIndex userRecordIndex=_actorsModel->recordIndexForID(user->getID());
    if(userRecordIndex.isValid())_actorsModel->setData(userRecordIndex,user->userName()+QString(" (disconnected)"),FKModelRole::nameRole);
}

void FKRoom::reconnectClient(FKObject* client,const QList<FKUser*> &users){
    if(!servant){
        FK_MLOG("unable reconnect client: no servant")
        return;
    }
    servant->loadingClients[client->getID()]=FKRoomStates::nonLoaded;
    refreshActorsModel(client);
    switch(_state){
    case FKRoomStates::nonLoaded:
        break;
    case FKRoomStates::coreLoaded:
    case FKRoomStates::loaded:{
//        qint8 s=servant->loadingClients.value(client->getID(),FKRoomStates::clientNotFound);
//        if(s!=FKRoomStates::nonLoaded){
//            FK_MLOGV("invalid reconnecting client room state",s)
//            return;
//        }
        servant->loadingClients[client->getID()]=FKRoomStates::coreLoading;
        doEvent(this,FKEventName::e_beginLoading,client);
        doEvent(this,FKEventName::e_loadScene,client);
        foreach(FKUser* u,users)loadUser(u);
        doEvent(this,FKEventName::e_loadingComplete,client);}
        break;
    default:
        FK_MLOGV("unexpected room state while reconnecting",_state)
    }
}

void FKRoom::refreshActorsModel(FKObject *client){
    doEvent(this,FKEventName::e_reinstallActorsModel,_actorsModel->retriveData(),client);
}

FKActorsModel* FKRoom::actorsModel() const{
    return _actorsModel;
}

QString FKRoom::roomName() const{
    return _name;
}

void FKRoom::setRoomName(QString roomName){
    _name=roomName;
//    _roomNames[getID()]=roomName;
}

QList<QMap<int,QVariant> > FKRoom::roomList(){
//    _roomNames.insert(1,QString("dummyRoom"));
    QList<QMap<int,QVariant> > r;
    foreach(FKRoom* room,_roomsExist){
        if(!room->started()){
            QMap<int,QVariant> lst;
            lst[FKModelRole::objectID]=room->getID();
            lst[FKModelRole::roomType]=room->roomType();
            lst[FKModelRole::nameRole]=room->roomName();
            r.append(lst);
        }
    }
    return r;
}

FKRoom* FKRoom::createRoom(QStringList roomData){
    if(roomData.size()!=4)return 0;
    const QString roomName(roomData.at(1));
    const QString roomType(roomData.at(0));
//    FK_MLOGV("room",roomName+roomType)
//    const QString description(roomData.at(2));
//    const QString password(roomData.at(3));
    if(roomName==QString())return 0;
//    FKRoom* r=new FKRoom;
    FKRoomModule* m=_avaliableModules.value(roomType,0);
    if(!m)return 0;
    m->loadModule();
    FKRoom* r=qobject_cast<FKRoom*>(createObject(roomType,-1));
    if(!r)return 0;
    r->servant=new Servant;
    r->setRoomName(roomName);
    r->updateProp(FKEventName::c_name);
    r->applyUpdates();
    connect(r->_actorsModel,SIGNAL(updateDataRequest(FKModelData)),r,SLOT(actorsModelDataChanged(FKModelData)));
    return r;
}

void FKRoom::setActive(const bool &t){
    if(servant){
        servant->isActive=t;
        if(t){
            foreach(FKObject* obj, _objects)obj->initObject();
        }else{
            foreach(FKObject* obj, _objects)obj->deinitObject();
        }
    }
}

void FKRoom::initRoomModule(const QString &roomClass){
    FKRoomModule* m=_avaliableModules.value(roomClass,0);
    if(m)m->loadModule();
}

void FKRoom::E_publicMessage(FKEvent* event){
    QString message(event->value().toString());
    writeChatLog(message);
//    FKUser* u=qobject_cast<FKUser*>(getObject(currentEvent()->owner()));
    _messageHistory.append(message);
    if(_messageHistory.size()>_messageHistoryCapacity)_messageHistory.removeFirst();
    emit gotPublicMessage(message);
}

void FKRoom::writeChatLog(const QString &msg){
    QFile file(_chatLogFile);
//    FK_MLOGV("file opening",_chatLogFile)
    if(file.open(QIODevice::ReadWrite)){
//        FK_MLOG("file opened")
        file.seek(file.size());
        file.write((msg+QString("\n")).toUtf8());
        file.close();
    }
}

//void FKRoom::E_addActor(FKEvent *event){
//    QList<QVariant> lst=event->value().toList();
//    actorsModel()->setActorID(lst.at(0).toInt(),lst.at(1).toInt(),lst.at(2).toInt());
//}

void FKRoom::A_beginLoading(FKEvent* event){
    Q_UNUSED(event)
    if(!servant){
        FK_MLOG("cannot process begin loading action: no servant")
        return;
    }
    if(_state!=FKRoomStates::nonLoaded)return;
    if(!startCondition()){
        FK_MLOG("start conditions failed")
        return;
    }
    _state=FKRoomStates::coreLoading;
    foreach(FKObject* cl,totalVisors())servant->loadingClients[cl->getID()]=FKRoomStates::coreLoading;
    doEvent(this,FKEventName::e_loadScene,FKObject::ToAllClients);
    doEvent(this,FKEventName::e_beginLoading,FKObject::ToAllClients);
    coreLoading();
}

void FKRoom::E_loadScene(FKEvent* event){
    Q_UNUSED(event)
    emit loadSceneRequested();
}

void FKRoom::E_beginLoading(FKEvent* event){
    Q_UNUSED(event)
    _state=FKRoomStates::coreLoading;
    emit loadingBegan(20,20);
}

void FKRoom::coreLoading(){
    loadCore();
    initStart();
    foreach(FKUser* u,_users)loadUser(u);
    _state=FKRoomStates::coreLoaded;
    doEvent(this,FKEventName::e_loadingComplete,FKObject::ToAllClients);
}

void FKRoom::sceneLoaded(){
    _sceneLoaded=true;
    if(_state>=FKRoomStates::coreLoaded){
        FK_MLOG("scene loaded after core")
        initObjects();
    }else{
        FK_MLOG("scene loaded, but core was not")
    }
}

void FKRoom::E_loadingComplete(FKEvent *event){
    Q_UNUSED(event)
    _state=FKRoomStates::coreLoaded;
    if(_sceneLoaded){
        FK_MLOG("core loaded after scene")
        initObjects();
    }else{
        FK_MLOG("core loaded, but scene was not")
    }
}

void FKRoom::initObjects(){
    foreach(FKObject* obj,_objects)obj->initObject();
    emit loadingComplete();
}

void FKRoom::A_clientLoadingComplete(FKEvent *event){
    if(!servant){
        FK_MLOG("cannot process client loading complete action: no servant")
        return;
    }
    qint8 currentState=servant->loadingClients.value(event->client(),FKRoomStates::clientNotFound);
    switch(currentState){
    case FKRoomStates::coreLoading:
        servant->loadingClients[event->client()]=FKRoomStates::coreLoaded;
        if(_state==FKRoomStates::coreLoaded){
            clientLoadingComplete();
        }else if(_state==FKRoomStates::loaded){
            servant->loadingClients[event->client()]=FKRoomStates::loaded;
            doEvent(this,FKEventName::e_endLoading,getObject(event->client()));
        }else{
            FK_MLOGV("unexpected room state while reconnecting complete action",_state)
        }
        break;
    case FKRoomStates::coreLoaded:
        FK_MLOGV("dublicate clientLoadingComplete action",event->client())
        break;
    case FKRoomStates::clientNotFound:
        FK_MLOGV("cannot find completed loading client",event->client())
        break;
    default:
        FK_MLOGV("uncknown error for completed loading client",event->client())
    }
}

void FKRoom::clientLoadingComplete(){
    for(QMap<qint32,qint8>::const_iterator i=servant->loadingClients.constBegin();i!=servant->loadingClients.constEnd();++i){
        if(i.value()!=FKRoomStates::coreLoaded)return;
    }
    _state=FKRoomStates::loaded;
    for(QMap<qint32,qint8>::iterator i=servant->loadingClients.begin();i!=servant->loadingClients.begin();++i){
        i.value()=FKRoomStates::loaded;
    }
    doEvent(this,FKEventName::e_endLoading,FKObject::ToAllClients);
}

void FKRoom::E_endLoading(FKEvent* event){
    Q_UNUSED(event)
    _state=FKRoomStates::loaded;
    emit loadingEnded();
}

void FKRoom::loadCore(){
    for(int i=0;i<10;){
        thread()->usleep(100000);
        emit loadingProgress(20,++i);
    }
}

void FKRoom::loadUser(FKUser* user){
    Q_UNUSED(user)
}

void FKRoom::initStart(){
    for(int i=10;i<20;){
        thread()->usleep(100000);
        emit loadingProgress(20,++i);
    }
}

void FKRoom::E_loadingProgress(FKEvent *event){
    Q_UNUSED(event)
}

void FKRoom::E_updateActorsModel(FKEvent* event){
    FKModelData data(event->value().value<FKModelData>());
    _actorsModel->updateData(data);
}

void FKRoom::E_reinstallActorsModel(FKEvent *event){
    FKDataRecord* r=new FKDataRecord(0,event->value());
    _actorsModel->installNewSource(r);
}

void FKRoom::e_setMessageHistory(QStringList history){
    _messageHistory=history;
    emit messageHistoryUpdated(history);
}

//void FKRoom::userGone(FKUser* user){
//    Q_UNUSED(user)
//}

void FKRoom::connectInterface(QObject *roomInterface){
    Q_UNUSED(roomInterface)
}

void FKRoom::actorsModelDataChanged(FKModelData d){
    doEvent(this,FKEventName::e_updateActorsModel,QVariant::fromValue<FKModelData>(d),ToDistantClients);
}

void FKRoom::loadActors(){
    FKActorsModel* am=_actorsModel;
    for(int i=0;i<2;++i){
        FKActor* a=new FKActor(this);
        connect(a,SIGNAL(won(FKActor*)),SLOT(actorWin(FKActor*)));
        connect(a,SIGNAL(loose(FKActor*)),SLOT(actorLose(FKActor*)));
        FKUser* u=qobject_cast<FKUser*>(getObject(am->data(am->recordIndexForRow(i),FKModelRole::objectID).toInt()));
        _actors.append(a);
        QModelIndex actorIndex=am->actorIndexForUserID(u->getID());
        am->setData(actorIndex,a->getID(),FKModelRole::objectID);
        a->allowEmitBy(u);
        am->setData(actorIndex,0,FKModelRole::score);
    }
}

void FKRoom::actorWin(FKActor *actor){
    QModelIndex actorIndex=actorsModel()->recordIndexForID(actor->getID(),FKActorsModel::ActorRecord);
    int oldScore=actorsModel()->data(actorIndex,FKModelRole::score).toInt();
    actorsModel()->setData(actorIndex,oldScore+1,FKModelRole::score);
    doEvent(this,FKEventName::e_win,QVariant(),actor);
}

void FKRoom::actorLose(FKActor *actor){
    doEvent(this,FKEventName::e_lose,QVariant(),actor);
}

void FKRoom::E_win(FKEvent *event){
    Q_UNUSED(event)
    emit actorMessage(QString("You are winner"));
}

void FKRoom::E_lose(FKEvent *event){
    Q_UNUSED(event)
    emit actorMessage(QString("You are looser"));
}
